package main;

/*

Program ::= StatList
StatList ::= { Stat }
Stat ::= AssigStat | SeStat | RepitaStat | EnquantoStat | ParaStat | ActorStat
AssigStat ::= [ "@" ] Ident [ "[" Expr "]" ] "=" Expr
SeStat ::= "se" Expr
                StatList
            [ "senão" ]
                StatList
Repita ::= "repita" Expr
                StatList
EnquantoStat ::= "enquanto" Expr "faça"
                       StatList
ParaStat ::= "para" Ident "=" Expr "até" Expr "faça"
                   StatList
ActorStat ::= "mova" Expr | "vire" Expr | "abaixa" | "levanta" | "apaga"
ExprList ::=  Expr { "," Expr }
Ident ::= Letter { Letter | Digit }
Expr ::= AndExpr [ "ou" AndExpr ]
AndExpr ::= RelExpr [ "e" RelExpr ]
RelExpr ::= AddExpr [ RelOp AddExpr ]
AddExpr ::= MultExpr { AddOp MultExpr }
MultExpr ::= SimpleExpr { MultOp SimpleExpr }
SimpleExpr ::= Number | "verdadeiro" | "falso"
           | "(" Expr ")" | "não" SimpleExpr | AddOp SimpleExpr
           | Ident [ "(" ExprList ")" ]
           | [ "@" ] Ident [ "[" Expr "]" ]
           | LiteralString | LiteralCharacter
RelOp ::= "<" | "<=" | ">" | ">="| "==" | "!="
AddOp ::= "+"| "-"
MultOp ::= "*" | "/" | "%"
Number ::= ["+"|"-"] Digit { Digit }
Digit ::= "0"| "1" | ... | "9"
Letter ::= "A" | "B"| ... | "Z"| "a"| "b" | ... | "z"
LiteralString ::= '"' { Character } '"'
LiteralCharacter ::= "'" Character "'"

*/

import AuxComp.*;
import asa.*;

import java.util.*;
import Lexer.*;


public class Compiler {

      // compile must receive an input with an character less than
      // p_input.lenght
    public Program compile( char []input ) {

        symbolTable = new SymbolTable();
        compilationErrorList = new CompilationErrorList( lexer );
        lexer = new Lexer(input, compilationErrorList);
        compilationErrorList.setLexer(lexer);

        lexer.nextToken();

        Program p = null;
        try {
          p = program();
          if ( lexer.token != Symbol.EOF )
        	  compilationErrorList.addError("comandos após o fim do programa");
        } catch ( Exception e ) {
              // the below statement prints the stack of called methods.
              // of course, it should be removed if the compiler were
              // a production compiler.
            //e.printStackTrace();
        }
        if ( compilationErrorList.wasAnErrorSignalled() )
          return null;
        else
          return p;
        }

/*
	Program ::= StatList
    StatList ::= Stat { ";" Stat }
    Stat ::= AssigStat | SeStat | RepitaStat | EnquantoStat | ParaStat | ActorStat |
         CompositeStat
	AssigStat ::= [ "@" ] Ident [ "[" Expr "]" ] "=" Expr

 */

    private Program program() {
    	return new Program(statementList(), symbolTable);
    }

    private StatementList statementList() {
    	ArrayList<Statement> statList = new ArrayList<Statement>();

    	while ( startStatement(lexer.token) ) {
    		statList.add(statement());
    		if ( lexer.token != Symbol.SEMICOLON )
    			compilationErrorList.addWarning("; esperado");
    		else
    		    lexer.nextToken();

    	}
    	return new StatementList(statList);
    }

    private Statement statement() {

        switch (lexer.token) {
            case IDENT :
            case AT :
                return assignmentStatement();
            case LEFTSQBRACKET :
            	return compositeStatement();
            case SE :
            	return seStatement();
            case REPITA :
            	return repitaStatement();
            case ENQUANTO :
            	return enquantoStatement();
            case PARA :
            	return paraStatement();
            case MOVA :
            	lexer.nextToken();
            	return new MovaActorStat( expr() );
            case VIRE :
            	lexer.nextToken();
            	return new VireActorStat(expr());
            case ABAIXA :
            	lexer.nextToken();
            	return new AbaixaActorStat();
            case LEVANTA :
            	lexer.nextToken();
            	return new LevantaActorStat();
            case APAGA :
            	lexer.nextToken();
            	return new ApagaActorStat();
            default :
                compilationErrorList.addError("Instrução esperada");
                return null;

        }
    }

    private boolean startStatement(Symbol t) {
    	return t == Symbol.IDENT ||
    	       t == Symbol.AT ||
    	       t == Symbol.LEFTSQBRACKET ||
    	       t == Symbol.SE ||
    	       t == Symbol.REPITA ||
    	       t == Symbol.ENQUANTO ||
    	       t == Symbol.PARA ||
    	       t == Symbol.MOVA ||
    	       t == Symbol.VIRE ||
    	       t == Symbol.ABAIXA ||
    	       t == Symbol.LEVANTA ||
    	       t == Symbol.APAGA ;

    }

    /*
     * CompositeStat ::= "[" StatList "]"
     */
    private CompositeStatement compositeStatement() {
    	if ( lexer.token != Symbol.LEFTSQBRACKET )
    		compilationErrorList.addError("'[' esperado");
    	lexer.nextToken();
    	StatementList sl = statementList();
    	if ( lexer.token != Symbol.RIGHTSQBRACKET )
    		compilationErrorList.addError("']' esperado");
    	lexer.nextToken();
    	return new CompositeStatement(sl);
    }

    /*
    	AssigStat ::= [ "@" ] Ident [ "[" Expr "]" ] "=" Expr
     */

    private AssignmentStatement assignmentStatement() {

    	boolean at = false;
    	if ( lexer.token == Symbol.AT ) {
    		lexer.nextToken();
    		at = true;
    	}
    	if ( lexer.token != Symbol.IDENT )
    		compilationErrorList.addError("Identificador esperado após @");
    	String ident = lexer.getStringValue();
        Variable v = new Variable(ident);
        if ( at )
        	symbolTable.putStatic(ident, v);
        else
        	symbolTable.put(ident, v);

        VariableExpr ve = new VariableExpr(v);
        lexer.nextToken();
        if ( lexer.token == Symbol.LEFTSQBRACKET ) {
        	lexer.nextToken();
        	ve.setArrayIndex(expr());
        	if ( lexer.token != Symbol.RIGHTSQBRACKET )
        		compilationErrorList.addError("] esperado depois do índice do vetor");
        	lexer.nextToken();
        }

        if ( lexer.token != Symbol.ASSIGN )
          compilationErrorList.addError("= expected");
        else
          lexer.nextToken();
        Expr right = expr();
        return new AssignmentStatement( ve, right );
    }

        /*
     * 	SeStat ::= "se" Expr  "então"
			StatList
		[ "senão" ]
			StatList
		"fimse"

     */
    private SeStat seStatement() {
    	lexer.nextToken();
    	Expr e = expr();
    	CompositeStatement entaoList = compositeStatement();
    	CompositeStatement senaoList = null;
    	if ( lexer.token == Symbol.SENAO ) {
        	lexer.nextToken();
        	senaoList = compositeStatement();
    	}

    	return new SeStat(e, entaoList, senaoList);

    }


    /**
     * Repita ::= "repita" Expr CompositeStat
     *
     * @return
     */
    private RepitaStat repitaStatement() {
    	lexer.nextToken();
    	Expr e = expr();
    	return new RepitaStat(e, compositeStatement());
    }


    /**
         EnquantoStat ::= "enquanto" Expr "faça" CompositeStat
     * @return
     */
    private EnquantoStat enquantoStatement() {
    	lexer.nextToken();
    	Expr e = expr();
    	if ( lexer.token != Symbol.FACA )
    		compilationErrorList.addError("'faça' expected");
    	lexer.nextToken();
    	return new EnquantoStat(e, compositeStatement());

    }


    /**
       ParaStat ::= "para" Ident "=" Expr "até" Expr "faça"   CompositeStat
     * @return
     */
    private ParaStat paraStatement() {

    	lexer.nextToken();
    	if ( lexer.token != Symbol.IDENT )
    		compilationErrorList.addError("Identificador esperado");
    	String ident = lexer.getStringValue();
    	lexer.nextToken();
    	if ( lexer.token != Symbol.ASSIGN )
    		compilationErrorList.addError("'=' esperado");
    	lexer.nextToken();
    	Expr from = expr();
    	if ( lexer.token != Symbol.ATE )
    		compilationErrorList.addError("'até' esperado");
    	lexer.nextToken();
    	Expr to = expr();
    	if ( lexer.token != Symbol.FACA )
    		compilationErrorList.addError("'faça' esperado");
    	lexer.nextToken();
    	CompositeStatement cs = compositeStatement();
    	return new ParaStat(new Variable(ident), from, to, cs);


    }

    private Expr expr() {
      /*
        OrExpr ::= AndExpr [ "or" AndExpr ]
      */

      Expr left, right;
      left = andExpr();
      if ( lexer.token == Symbol.OU ) {
        lexer.nextToken();
        right = andExpr();
          // semantic analysis
        left = new CompositeExpr(left, Symbol.OU, right);
      }
      return left;
    }

    private Expr andExpr() {
      /*
        AndExpr ::= RelExpr [ "and" RelExpr ]
      */
      Expr left, right;
      left = relExpr();
      if ( lexer.token == Symbol.E ) {
        lexer.nextToken();
        right = relExpr();
        left = new CompositeExpr( left, Symbol.E, right );
      }
      return left;
    }

    private Expr relExpr() {
      /*
        RelExpr ::= AddExpr [ RelOp AddExpr ]
      */
      Expr left, right;
      left = addExpr();
      Symbol op = lexer.token;
      if ( op == Symbol.EQ || op == Symbol.NEQ || op == Symbol.LE || op == Symbol.LT ||
           op == Symbol.GE || op == Symbol.GT  ) {
         lexer.nextToken();
         right = addExpr();
         left = new CompositeExpr( left, op, right );
       }
       return left;
    }

    private Expr addExpr() {
      /*
        AddExpr ::= MultExpr { AddOp MultExpr }

      */
      Symbol op;
      Expr left, right;
      left = multExpr();
      while ( (op = lexer.token) == Symbol.PLUS ||
              op == Symbol.MINUS ) {
        lexer.nextToken();
        right = multExpr();
        left = new CompositeExpr( left, op, right );
      }
      return left;
    }

    private Expr multExpr() {
     /*
        MultExpr ::= SimpleExpr { MultOp SimpleExpr }
     */
       Expr left, right;
       left = simpleExpr();
       Symbol op;
       while ( (op = lexer.token) == Symbol.MULT ||
               op == Symbol.DIV || op == Symbol.REMAINDER ) {
          lexer.nextToken();
          right = simpleExpr();
          left = new CompositeExpr( left, op, right );
       }
       return left;
    }

    private Expr simpleExpr() {
      /*
SimpleExpr ::= Number | "verdadeiro" | "falso"
           | "(" Expr ")" | "não" SimpleExpr | AddOp SimpleExpr
           | Ident [ "(" ExprList ")" ]
           | [ "@" ] Ident [ "[" Expr "]" ]
           | LiteralString
           | LiteralCharacter
      */

      Expr e;
      boolean at = false;
      Variable v;
	  VariableExpr ve;

        // note we test the lexer.getToken() to decide which production to use
      switch ( lexer.token ) {
         case NUMBER :
            return number();
         case VERDADEIRO :
           lexer.nextToken();
           return BooleanExpr.True;
         case FALSO :
           lexer.nextToken();
           return BooleanExpr.False;
         case LITERAL_CHARACTER :
             // get the token with getToken.
             // then get the value of it, with has the type Object
             // convert the object to type Character using a cast
             // call method charValue to get the character inside the object
           char ch = lexer.getCharValue();
           lexer.nextToken();
           return new CharExpr(ch);
         case LEFTPAR :
           lexer.nextToken();
           e = expr();
           if ( lexer.token != Symbol.RIGHTPAR ) {
             compilationErrorList.addError(") expected");
           }
           else
             lexer.nextToken();
           return new ParenthesisExpr(e);
         case NAO :
           lexer.nextToken();
           e = expr();
           return new UnaryExpr( e, Symbol.NAO );
         case PLUS :
           lexer.nextToken();
           e = expr();
           return new UnaryExpr( e, Symbol.PLUS );
         case MINUS :
           lexer.nextToken();
           e = expr();
           return new UnaryExpr( e, Symbol.MINUS );
         case LITERAL_STRING :
        	 String s = lexer.getLiteralString();
        	 return new LiteralStringExpr(s);
         case AT :
        	 at = true;
        	 lexer.nextToken();
         default :
        	 /*
                 Ident [ "(" ExprList ")" ]
                 [ "@" ] Ident [ "[" Expr "]" ]
        	  */
             // an identifier
           if ( lexer.token != Symbol.IDENT ) {
             compilationErrorList.addError("Identifier expected");
             return null;
           }
           else {
        	   String ident = lexer.getStringValue();
        	   lexer.nextToken();
        	   switch ( lexer.token ) {
        	   case LEFTPAR :
        		   if ( at )
        			   compilationErrorList.addError("Chamada de função precedida por '@'");
        		   lexer.nextToken();
        		   ExprList eList = exprList();
        		   if ( lexer.token != Symbol.RIGHTPAR )
        			   compilationErrorList.addError("')' esperado");
        		   else
        			   lexer.nextToken();
        		   return new FunctionCall(ident, eList);
        	   case LEFTSQBRACKET :
        		   lexer.nextToken();
        		   e = expr();
        		   if ( lexer.token != Symbol.RIGHTSQBRACKET )
        			   compilationErrorList.addError("']' esperado");
        		   else
        			   lexer.nextToken();
        		   v = new Variable(ident);
        		   v.setStatic(at);
        		   ve = new VariableExpr(v);
        		   ve.setArrayIndex(e);
        		   return ve;
        	   default:
        		   v = new Variable(ident);
        		   v.setStatic(at);
        		   return new VariableExpr(v);
        	   }
           }
      }

    }

    ExprList exprList()
    {
       ExprList anExprList;
       if ( lexer.token == Symbol.RIGHTPAR )
          return null;
       else {
          anExprList = new ExprList();
          while ( true ) {
            Expr anExpr = expr();
            anExprList.addElement( anExpr );
            if ( lexer.token == Symbol.COMMA )
              lexer.nextToken();
            else
              break;
          }
          return anExprList;
       }
    }


    private NumberExpr number() {

        // the number value is stored in lexer.getToken().value as an object of Integer.
          // Method intValue returns that value as an value of type int.
        int value = lexer.getNumberValue();
        lexer.nextToken();
        return new NumberExpr( value );
    }


	public CompilationErrorList getCompilationErrorList() {
		return compilationErrorList;
	}

    private SymbolTable symbolTable;
    private Lexer lexer;
    private CompilationErrorList compilationErrorList;
}
